package org.jeecg.cn.edu.scau.vm.node.service.impl;

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.jeecg.cn.edu.scau.vm.common.consts.ContainerConst;
import org.jeecg.cn.edu.scau.vm.common.consts.NodeConst;
import org.jeecg.cn.edu.scau.vm.common.consts.RedisConst;
import org.jeecg.cn.edu.scau.vm.common.utils.RedisIdWorker;
import org.jeecg.cn.edu.scau.vm.container.domain.VmContainer;
import org.jeecg.cn.edu.scau.vm.container.enums.ContainerStatusEnum;
import org.jeecg.cn.edu.scau.vm.container.service.VmContainerBizService;
import org.jeecg.cn.edu.scau.vm.node.domain.*;
import org.jeecg.cn.edu.scau.vm.node.enums.NodeStatusEnum;
import org.jeecg.cn.edu.scau.vm.node.factory.SSHSessionFactory;
import org.jeecg.cn.edu.scau.vm.node.mapper.VmNodeMapper;
import org.jeecg.cn.edu.scau.vm.node.service.NodeService;
import org.jeecg.cn.edu.scau.vm.node.vo.*;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.service.ISysDepartService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.jeecg.cn.edu.scau.vm.common.consts.RedisConst.*;

@Service
public class NodeServiceImpl
        extends ServiceImpl<VmNodeMapper, VmNode> implements NodeService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private VmContainerBizService vmContainerBizService;
    @Resource
    private ISysDepartService sysDepartService;

    @Override
    public List<DisksInfo> getDiskInfoByNodeId(String id) {
        // 1. 获取key值,并且解析相关数据
        String diskInfoKey = id + RedisConst.NODE_DISK_KEY_PREFIX;
        List<String> diskRawInfo = stringRedisTemplate.opsForList().range(diskInfoKey, 0, -1);
        if (Objects.isNull(diskRawInfo) || diskRawInfo.isEmpty()) {
            return Collections.emptyList();
        }
        return diskRawInfo.stream()
                .map((r) -> JSON.parseObject(r, DisksInfo.class))
                .collect(Collectors.toList());
    }

    @Override
    public List<NetworkInfo> getNetworkInfoByNodeId(String id) {
        String networkInfoKey = id + RedisConst.NODE_NETWORK_KEY_PREFIX;
        List<String> networkRawInfo = stringRedisTemplate.opsForList().range(networkInfoKey, 0, -1);
        if (Objects.isNull(networkRawInfo) || networkRawInfo.isEmpty()) {
            return Collections.emptyList();
        }
        return networkRawInfo.stream()
                .map((r) -> JSON.parseObject(r, NetworkInfo.class))
                .collect(Collectors.toList());
    }

    @Override
    public OSInfo getOSInfoByNodeId(String id) {
        String osInfoKey = id + RedisConst.NODE_OS_KEY_PREFIX;
        String raw = stringRedisTemplate.opsForValue().get(osInfoKey);
        if (Objects.isNull(raw) || raw.isEmpty()) {
            return new OSInfo();
        }
        return JSON.parseObject(raw, OSInfo.class);
    }

    @Override
    public OSRuntimeInfo getOSRuntimeInfoByNodeId(String id) {
        String runtimeInfoKey = id + NODE_RUNTIME_KEY_PREFIX;
        Long size = stringRedisTemplate.opsForList().size(runtimeInfoKey);
        logger.info("OSRuntimeInfo,获得的size为:{}", size);
        if (Objects.isNull(size) || size == 0) {
            return new OSRuntimeInfo();
        }
        String raw = stringRedisTemplate.opsForList().index(runtimeInfoKey, size - 1);
        logger.info("OSRuntimeInfo,获得的rawString为:{}", raw);
        if (Objects.isNull(raw) || raw.isEmpty()) {
            return new OSRuntimeInfo();
        }
        return JSON.parseObject(raw, OSRuntimeInfo.class);
    }

    public static final String SEARCH_KEY = "*" + NODE_LOAD_PREFIX;
    private static final Logger logger = LoggerFactory.getLogger(NodeServiceImpl.class);
    private static final String AGENT_INSTALL_CMD = "docker run -d --network=host -e VM_HOST_ADDRESS=%s -e VM_HOST_NODE_ID=%s -e VM_HOST_USERNAME=%s -e VM_HOST_PASSWORD=%s -e VM_HOST_PORT=%s --name vm_agent 202.116.161.93:5000/vm_agent";
    private static final String SEP = ";";

    @Resource(name = "readStreamThreadPool")
    private ThreadPoolExecutor readStreamThreadPool;

    private List<OSRuntimeInfo> getLoadDataList(String id) {
        String key = id + NODE_RUNTIME_KEY_PREFIX;
        List<String> rawStrings = stringRedisTemplate.opsForList().range(key, 0, -1);
        if (Objects.isNull(rawStrings) || rawStrings.isEmpty()) {
            return Collections.emptyList();
        }
        return rawStrings.stream()
                .map((rawString) -> JSON.parseObject(rawString, OSRuntimeInfo.class))
                .collect(Collectors.toList());
    }


    @Override
    public Result<List<VmNodeCpuChartVo>> getCpuDataById(String id) {
        List<OSRuntimeInfo> loadDataList = this.getLoadDataList(id);
        List<VmNodeCpuChartVo> collect = loadDataList.stream().map((loadData) -> VmNodeCpuChartVo.builder()
                .name(loadData.getTimestamp())
                .value(loadData.getCpuUsage() * 100)
                .build()).collect(Collectors.toList());
        return Result.ok(collect);
    }

    @Override
    public Result<List<VmNodeRamChartVo>> getRamDataById(String id) {
        List<OSRuntimeInfo> loadDataList = this.getLoadDataList(id);
        List<VmNodeRamChartVo> collect = loadDataList.stream().map((loadData) -> VmNodeRamChartVo.builder()
                .name(loadData.getTimestamp())
                .value((loadData.getUsedMemory() * 1.0D / loadData.getTotalMemory()) * 100)
                .build()).collect(Collectors.toList());
        return Result.ok(collect);
    }

    @Override
    public Result<List<VmNodeDiskChartVo>> getDiskDataById(String id) {
        String key = id + NODE_DISK_KEY_PREFIX;
        List<String> rawStrings = stringRedisTemplate.opsForList().range(key, 0, -1);
        if (Objects.isNull(rawStrings) || rawStrings.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        List<VmNodeDiskChartVo> collect = rawStrings.stream().map((rawString) -> {
            DisksInfo disksInfo = JSON.parseObject(rawString, DisksInfo.class);
            logger.info("{}", disksInfo);
            return VmNodeDiskChartVo.builder()
                    .name(disksInfo.getDirName())
                    .value((disksInfo.getUsage()) * 100)
                    .restPPT((disksInfo.getUsage()) * 100)
                    .total((disksInfo.getTotal() / 1024 / 1024 / 1024) * 1.0D)
                    .used((disksInfo.getUsed() / 1024 / 1024 / 1024) * 1.0D)
                    .build();
        }).collect(Collectors.toList());
        return Result.ok(collect);
    }

    @Override
    public Result<VmNodeBasicData> getLoadDataById(String nodeId) {
        // 通过id信息获取负载信息
        VmNode nodeByNodeId = this.getNodeByNodeId(nodeId);
        OSInfo osInfoByNodeId = this.getOSInfoByNodeId(nodeId);
        logger.info("解析获得的OSInfo:{}", osInfoByNodeId);
        OSRuntimeInfo osRuntimeInfoByNodeId = this.getOSRuntimeInfoByNodeId(nodeId);
        logger.info("获得的OSRuntimeInfo:{}", osRuntimeInfoByNodeId);
        Integer systemRamOcu = (int) ((osRuntimeInfoByNodeId.getUsedMemory() * 1.0D /
                osRuntimeInfoByNodeId.getTotalMemory()) * 100);
        Integer virtualRamOcu = (int) ((osRuntimeInfoByNodeId.getSwapUsedMemory() * 1.0D /
                osRuntimeInfoByNodeId.getSwapTotalMemory()) * 100);
        VmNodeBasicData vmNodeBasicData = VmNodeBasicData.builder()
                .id(nodeByNodeId.getNodeId())
                .name(nodeByNodeId.getName())
                .ipAddress(nodeByNodeId.getNodeIpAddress())
                .statusName(this.getNodeStatus(nodeId).getMessage())
                .systemArc(osInfoByNodeId.getOsArch())
                .hostName(osInfoByNodeId.getHostName())
                .systemName(osInfoByNodeId.getOs())
                .systemRamOcu(systemRamOcu)
                .virtualRamOcu(virtualRamOcu)
                .gpuCount(String.valueOf(nodeByNodeId.getGpuNumber()))
                .cpuCount(String.valueOf(osInfoByNodeId.getCpuCount()))
                .build();
        return Result.ok(vmNodeBasicData);
    }

    @Override
    public Result<String> initNode(String nodeId) {
        // 1. 获取节点信息
        VmNode nodeByNodeId = this.getNodeByNodeId(nodeId);
        return this.initNode(nodeByNodeId);
    }

    @Override
    public NodeStatusEnum getNodeStatus(String nodeId) {
        // 1. 先获取数据库状态
        VmNode vmNode = this.getNodeByNodeId(nodeId);
        if (Objects.isNull(vmNode)) {
            return NodeStatusEnum.INIT_FAILED;
        }
        String nodeInfoKey = nodeId + ":node_info";
        Boolean hasKey = stringRedisTemplate.hasKey(nodeInfoKey);
        if (Boolean.TRUE.equals(hasKey)) {
            // 正常连接
            return NodeStatusEnum.NORMAL_CONNECTING;
        }
        // 2. 是否处于初始化状态
        if (vmNode.getStatus().equals(NodeStatusEnum.INIT_SUCCESS.getCode()) ||
                vmNode.getStatus().equals(NodeStatusEnum.INIT_FAILED.getCode()) ||
                vmNode.getStatus().equals(NodeStatusEnum.TOBE_INIT.getCode())) {
            return NodeConst.getEnumByCode(vmNode.getStatus());
        }
        // 3. 并非处于初始化状态,那么就执行一次探活,是否正常连接
        // 获取节点负载信息的key
        return NodeStatusEnum.ERROR_CONNECTING;
    }

    @Override
    public VmNode selectNode() {
        // 1. 列出所有在redis中有存储负载信息的节点
        Set<String> keys = stringRedisTemplate.keys(SEARCH_KEY);
        if (Objects.isNull(keys) || keys.isEmpty()) {
            logger.info("[选择节点]没有找到节点的负载信息");
            return null;
        }
        PriorityQueue<NodeLoadData> priorityQueue = new PriorityQueue<>();
        // 2. 查询负载信息
        keys.forEach((key) -> {
            Long index = stringRedisTemplate.opsForList().size(key);
            if (!Objects.isNull(index)) {
                // 3. 获取负载信息json字符串
                String loadJsonStr = stringRedisTemplate.opsForList().index(key, index - 1);
                NodeLoadData nodeLoadData = JSON.parseObject(loadJsonStr, NodeLoadData.class);
                priorityQueue.offer(nodeLoadData);
            }
        });
        // 4. 获取最优节点
        NodeLoadData bestNodeInfo = priorityQueue.poll();
        if (Objects.isNull(bestNodeInfo)) {
            return null;
        }
        logger.info("[选择节点]最优节点为:{}", bestNodeInfo.getNodeId());
        // 获取节点信息
        QueryWrapper<VmNode> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("node_uuid", bestNodeInfo.getNodeId());
        return this.getOne(queryWrapper);
    }


    private static class ReadStreamTask implements Supplier<String> {
        private final StringBuilder message;
        private final String command;
        private final InputStream inputStream;

        public ReadStreamTask(InputStream inputStream, String command) {
            this.inputStream = inputStream;
            this.command = command;
            message = new StringBuilder();
        }

        @Override
        public String get() {
            logger.info("开始执行指令{}", command);
            BufferedReader inOut = null;
            try {
                inOut = new BufferedReader(new InputStreamReader(inputStream));
                String perLine;
                while ((perLine = inOut.readLine()) != null) {
                    message.append(perLine).append(SEP);
                    logger.info("执行指令:{},执行返回的信息是:{}", command, perLine);
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            } finally {
                if (!Objects.isNull(inOut)) {
                    try {
                        inOut.close();
                    } catch (IOException e) {
                        logger.error(e.getMessage(), e);
                    }
                }
                if (!Objects.isNull(inputStream)) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
            return message.toString();
        }
    }

    @Override
    public VmNode getNodeByNodeId(String nodeId) {
        QueryWrapper<VmNode> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("node_uuid", nodeId);
        return this.getOne(queryWrapper);
    }

    @Override
    public IPage<VmNodeListVo> pageList(Page<VmNode> page, QueryWrapper<VmNode> queryWrapper) {
        // 1. 先执行分页查询
        Page<VmNode> rawPage = this.page(page, queryWrapper);
        // 2. 执行联查
        List<VmNode> records = rawPage.getRecords();
        List<VmNodeListVo> result = records.stream().map((vmNode) -> {
            VmNodeListVo vmNodeListVo = new VmNodeListVo();
            BeanUtils.copyProperties(vmNode, vmNodeListVo);
            NodeStatusEnum nodeStatus = this.getNodeStatus(vmNode.getNodeId());
            vmNodeListVo.setStatus(nodeStatus.getCode());
            vmNodeListVo.setStatusName(nodeStatus.getMessage());
            vmNodeListVo.setPasswordBase64(Base64.getEncoder().encodeToString(vmNode.getPassword().getBytes(StandardCharsets.UTF_8)));
            logger.info("解析之后的base64串为:{}", vmNodeListVo.getPasswordBase64());
            List<VmContainer> vmContainers = vmContainerBizService.getByNodeId(vmNode.getNodeId());
            // 拆分数据
            List<VmContainerVo> collect = vmContainers.stream().map((vmContainer -> {
                VmContainerVo vmContainerVo = new VmContainerVo();
                vmContainerVo.setContainerId(vmContainer.getContainerId());
                vmContainerVo.setName(vmContainer.getName());
                vmContainerVo.setContainerDesc(vmContainer.getDescription());
                ContainerStatusEnum enumByCode = ContainerConst.getEnumByCode(vmContainer.getStatus());
                vmContainerVo.setContainerStatusType(enumByCode.getStatusType());
                vmContainerVo.setContainerStatusName(enumByCode.getMessage());
                vmContainerVo.setCreateTime(vmContainer.getCreateTime());
                return vmContainerVo;
            })).collect(Collectors.toList());
            vmNodeListVo.setContainerData(collect);
            return vmNodeListVo;
        }).collect(Collectors.toList());
        Page<VmNodeListVo> dstPage = new Page<>();
        BeanUtils.copyProperties(rawPage, dstPage, "record");
        dstPage.setRecords(result);
        return dstPage;
    }

    @Override
    public Result<String> saveNode(VmNode vmNode) {
        // 1. 生成唯一ID
        vmNode.setNodeId(redisIdWorker.nextId(NODE_ID_PREFIX));
        // 2. 存到数据库中
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        vmNode.setCreateBy(loginUser.getId());
        vmNode.setUpdateBy(loginUser.getId());
        vmNode.setCreateTime(LocalDateTime.now());
        vmNode.setUpdateTime(LocalDateTime.now());
        vmNode.setSysOrgCode(loginUser.getOrgCode());
        // 获取部门名称
        QueryWrapper<SysDepart> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("org_code", loginUser.getOrgCode());
        SysDepart depart = sysDepartService.getOne(queryWrapper);
        vmNode.setStatus(NodeStatusEnum.TOBE_INIT.getCode());
        vmNode.setSysOrgName(depart.getDepartName());
        vmNode.setId(null);
        this.save(vmNode);
        return Result.ok("添加成功");
    }

    @Override
    public Result<String> initNode(VmNode vmNode) {
        // 更改状态为初始化中
        vmNode.setStatus(NodeStatusEnum.INIT.getCode());
        this.updateById(vmNode);
        /*节点初始化任务下发*/
        // 1. 获取SSH连接
        Session session = null;
        Connection connection = SSHSessionFactory.getConnection(
                vmNode.getNodeIpAddress(),
                vmNode.getUsername(),
                vmNode.getPassword(),
                22);
        // 2. 执行初始化任务指令,安装agent
        try {
            session = connection.openSession();
            String cmd = String.format(
                    AGENT_INSTALL_CMD,
                    vmNode.getNodeIpAddress(),
                    vmNode.getNodeId(),
                    vmNode.getUsername(),
                    vmNode.getPassword(),
                    22
            );
            session.execCommand(cmd);
            InputStream stdout = session.getStdout();

            CompletableFuture.supplyAsync(
                    new ReadStreamTask(stdout, cmd), readStreamThreadPool
            ).thenAcceptAsync((message) -> {
                logger.info("指令执行完毕,开始完成更新操作,操作日志:{}", message);
                UpdateWrapper<VmNode> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("id", vmNode.getId());
                updateWrapper.set("status", NodeStatusEnum.INIT_SUCCESS.getCode());
                this.update(updateWrapper);
                logger.info("更新节点状态:{},{}", vmNode.getId(), NodeStatusEnum.INIT_SUCCESS);
            }, readStreamThreadPool);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } finally {
            connection.close();
            if (!Objects.isNull(session)) {
                session.close();
            }
        }
        return Result.ok("初始化任务执行中");
    }


}
